import { ComponentConfig } from './ComponentConfig'
import { h } from 'vue'
import type { VNodeArrayChildren, VNodeProps } from 'vue'
import type { Token } from 'markdown-it/index.js'
import MarkdownIt from 'markdown-it/index.js'

/**
 * 以markdownit库提供的markdown渲染基础上, 
 * 提供在markdown文档中, 通过tag包裹JSON串的方式渲染自定义组件的解析器
 * 例: ### 标题<custom_tag>{ props1: '', props2: '' }</custom_tag>---
 */
export class MarkdownExtendTagParser {
  md: MarkdownIt
  tag!: string
  tagName!: string
  tagOpen!: string
  tagClose!: string
  componentConfig!: ComponentConfig
  openRegex!: RegExp

  constructor(tagName: string, config: ComponentConfig) {
    if (!tagName) {
      throw new Error('MarkdownExtendTagParser: the name of tag is required')
    }
    if (!config || !config.instance) {
      throw new Error('MarkdownExtendTagParser: need ComponentConfig when creating!')

    }
    this.md = MarkdownIt()
    this.init(tagName, config)
  }

  private init(tagName: string, config: ComponentConfig) {
    this.initBaseProperty(tagName, config)
    this.initOpenRegex()
    this.initRules()
  }

  private initBaseProperty(tagName: string, config: ComponentConfig) {
    this.tag = `${tagName}_tag`
    this.tagName = tagName
    this.tagOpen = '<' + tagName + '>'
    this.tagClose = '</' + tagName + '>'
    this.componentConfig = config
  }

  private initOpenRegex() {
    const tagCloseLen = this.tagClose.length
    const regexArr = []
    for (let i = 1; i < tagCloseLen; i++) {
      regexArr.push(this.tagOpen.substring(0, i))
    }
    const pattern = regexArr.join('|')
    this.openRegex = new RegExp(pattern)
  }

  /**
   * 介入markdownit生成tokens的过程, 在规则中自定义以tag作为类型的token, 方便后续解析
   */
  private initRules() {
    const tagOpenLen = this.tagOpen.length
    const tagCloseLen = this.tagClose.length

    this.md.inline.ruler.before('text', this.tag, (state, silent) => {
      const pos = state.pos
      const max = state.posMax
      const src = state.src
      // 匹配 tagOpen 开头
      if (pos + tagOpenLen > max || src.slice(pos, pos + tagOpenLen) !== this.tagOpen) {
        return false
      }
      // 匹配 tagClose 结尾
      const endTagPos = src.slice(pos).indexOf(this.tagClose)
      if (endTagPos === -1 || endTagPos + tagCloseLen > max) {
        return false
      }
      const jsonContent = src.slice(pos + tagOpenLen, pos + endTagPos)
      try {
        JSON.parse(jsonContent)
      } catch (error) {
        console.error(`markdown渲染失败!`)
        return false
      }
      // 非静默模式下需要生成token，静默模式下只查看是否匹配规则而不生成token
      if (!silent) {
        // nesting=0: tag is self closing
        const token = state.push(this.tag, this.tagName, 0)
        token.content = jsonContent
      }
      // 跳过tagOpen和tagClose中间的所有部分
      state.pos = pos + endTagPos + tagCloseLen
      return true
    })
  }

  /**
   * parser核心渲染方法, 自定义处理由markdownit生成的令牌集合
   * @param tokens markdownit转换目标字符串得到的令牌集合
   * @returns vnode渲染节点
   */
  private renderTokens(tokens: Token[]): VNodeArrayChildren {
    const numOfToken = tokens.length
    const vNodeList = []
    for (let i = 0; i < numOfToken; i++) {
      const tokenRef = tokens[i]
      if (tokenRef.type === 'inline') {
        const children = tokenRef.children || []
        if (children.length > 0) {
          vNodeList.push(this.renderTokens(tokenRef.children || []))
        }
      } else if (tokenRef.type === this.tag) {
        const dataObj = JSON.parse(tokenRef.content)
        const componentNode = h(this.componentConfig.instance, this.componentConfig.getProps(dataObj), undefined)
        vNodeList.push(componentNode)
      } else if (tokenRef.type === 'text') {
        // 这一步是为了解决在流式输出时，只有tagOpen没有tagClose而导致将JSON串以字符串的形式渲染到页面上的问题
        // 直到tagOpen能够匹配到tagClose时，就会走上一分支而不会走当前分支
        if (!this.openRegex.test(tokenRef.content)) {
          vNodeList.push(tokenRef.content)
        }
      } else {
        if (tokenRef.nesting === 1) {
          const nextTokenRef = tokens[i + 1]
          if (nextTokenRef) {
            if (nextTokenRef.type === 'inline') {
              const nearestInlineChildren = tokens[i + 1].children || []
              if (nearestInlineChildren.length > 0) {
                vNodeList.push(h(tokenRef.tag, {}, this.renderTokens(nearestInlineChildren)))
                i++
              }
            } else {
              const endTagTokenPos = tokens.slice(i + 1).findIndex(token => token.tag === tokenRef.tag && token.nesting === -1)
              const renderRestNodes = this.renderTokens(tokens.slice(i + 1, i + endTagTokenPos + 1))
              vNodeList.push(h(tokenRef.tag, {}, renderRestNodes))
              i += endTagTokenPos + 1
            }
          }
        } else if (tokenRef.type === 'hr') {
          vNodeList.push(h('hr', {}, undefined))
        }
      }
    }
    return vNodeList
  }

  /**
   * 对外提供字符串转换tokens的功能
   * @param mdText 目标markdown字符串
   * @returns markdownit转换目标字符串得到的令牌
   */
  parseTokens(mdText: string): Token[] {
    return this.md.parse(mdText, {})
  }

  /**
   * 对外提供渲染功能
   * @param mdText 目标markdown字符串
   * @param topElementProps h函数中，顶层元素可配置的props
   * @returns h函数渲染结果
   */
  render(mdText: string, topElementProps: VNodeProps & Record<string, any>) {
    const tokens = this.parseTokens(mdText)
    return () => h('div', { ...topElementProps }, this.renderTokens(tokens))
  }
}